package upload

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"playbook/ginsvr/src/atom"
	"playbook/ginsvr/src/errno"
	"playbook/ginsvr/src/global"
	"playbook/ginsvr/src/orm"
	"reflect"
	"strings"

	"github.com/gin-gonic/gin"
	uuid "github.com/satori/go.uuid"
	"github.com/sirupsen/logrus"
)

func UploadAndParsel(c *gin.Context) errno.Error {
	// 上传文件
	filePath, err := upload(c)
	if err != nil {
		return err
	}

	// 删除项目的表数据
	errn := orm.DeleteByProjectId(c.PostForm("projectid"))
	if errn != nil {
		return errno.ErrFIleUpload().WithDetailMessage("delete table data failed.")
	}

	// 解析入库
	err, dataMap := parselFile(filePath, c)
	if err != nil {
		return err
	}

	// 入库
	err = insertToDb(dataMap)
	if err != nil {
		return err
	}

	// 生成Model与Mapper
	for tableName, fieldArr := range dataMap {
		errn = atom.GeneModelAndMapper(tableName, fieldArr)
		if errn != nil {
			logrus.Error("生成model与mapper类失败,tableName=" + tableName)
			return errno.ErrFIleUpload().WithDetailMessage("generate model source failed!")
		}
	}

	return errno.OK()
}

func parselFile(path string, c *gin.Context) (errno.Error, map[string][]*orm.Field) {
	fileHanle, err := os.OpenFile(path, os.O_RDONLY, 0666)
	if err != nil {
		global.LOG.WithFields(logrus.Fields{
			"err": err,
		}).Error("read file failed.")
		return errno.ErrFIleUpload().WithDetailMessage("read file failed."), nil
	}

	defer fileHanle.Close()

	reader := bufio.NewReader(fileHanle)
	projectId := c.PostForm("projectid")
	dataMap := make(map[string][]*orm.Field)
	var fields []*orm.Field
	var tableName string
	var lineStr string
	var startParimaryKey bool
	var startField bool = false
	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		}
		lineStr = strings.Trim(string(line), " ")
		if strings.HasPrefix(lineStr, "CREATE TABLE") && !startParimaryKey {
			startField = true
		}
		if lineStr == ")" && startField {
			dataMap[tableName] = fields
			fields = nil
		}

		// 解析字段信息
		if strings.HasPrefix(lineStr, "CREATE TABLE") {
			tableName = lineStr[strings.Index(lineStr, "\"public\".")+10 : strings.LastIndex(lineStr, "\"")]
			fields = make([]*orm.Field, 0)
		} else if startField && fields != nil {
			arr := strings.Split(lineStr, " ")
			field := orm.Field{
				Id:           uuid.NewV4().String(),
				ProjectId:    projectId,
				TableName:    tableName,
				FieldName:    replaceQuotes(arr[0]),
				FieldType:    replaceFieldTypeComma(replaceQuotes(arr[1])),
				IsPrimaryKey: "false",
			}
			fields = append(fields, &field)
		}

		if strings.HasPrefix(lineStr, "ALTER TABLE") && !startParimaryKey {
			startParimaryKey = true
			startField = false
		}

		// 解析主键信息
		if strings.HasPrefix(lineStr, "ALTER TABLE") && strings.Contains(lineStr, "PRIMARY KEY") && startParimaryKey {
			primaryArr := strings.Split(lineStr, " ")
			fieldArr := dataMap[splitTableName(primaryArr[2])]
			for _, one := range fieldArr {
				if one.FieldName == splitPrimaryKey(primaryArr[8]) {
					one.IsPrimaryKey = "true"
					break
				}
			}
		}
	}

	return nil, dataMap
}

func replaceFieldTypeComma(str string) string {
	return strings.Replace(str, ",", "", -1)
}

func replaceQuotes(str string) string {
	return strings.Replace(str, "\"", "", -1)
}

func splitTableName(str string) string {
	str = strings.Replace(str, "\"", "", -1)
	return str[strings.Index(str, ".")+1:]
}

func splitPrimaryKey(str string) string {
	str = strings.Replace(str, "\"", "", -1)
	return str[1:strings.Index(str, ")")]
}

func insertToDb(dataMap map[string][]*orm.Field) errno.Error {
	for _, fieldArr := range dataMap {
		err := orm.BatchSave(fieldArr)
		if err != nil {
			global.LOG.WithFields(logrus.Fields{
				"err": err,
			}).Error("insert into db failed.")
			return errno.ErrFIleUpload().WithDetailMessage("insert into db failed.")
		}
	}
	return nil
}

func upload(c *gin.Context) (string, errno.Error) {
	file, err := c.FormFile("file")
	if err != nil {
		global.LOG.WithFields(logrus.Fields{
			"err": err,
		}).Error("upload sql script failed!")
		return "", errno.ErrFIleUpload().WithDetailMessage("file upload failed.")
	}

	ext := strings.Split(file.Filename, ".")
	if !container(ext[1], [...]string{"sql"}) {
		global.LOG.WithFields(logrus.Fields{
			"fileType": ext[1],
		}).Error("file type does not matched!")

		return "", errno.ErrFIleUpload().WithDetailMessage("file type does not matched!")
	}

	filePath := fmt.Sprintf("./temp/%s/", c.PostForm("projectid"))
	_, err = os.Stat(filePath)
	if err != nil {
		if !os.IsExist(err) {
			err := os.MkdirAll(filePath, os.ModePerm)
			if err != nil {
				global.LOG.WithFields(logrus.Fields{
					"dir": filePath,
				}).Error("create dir failed.")
				return "", errno.ErrFIleUpload().WithDetailMessage("create dir failed.")
			}
		}
	}

	filePath = filePath + file.Filename
	err = c.SaveUploadedFile(file, filePath)
	if err != nil {
		global.LOG.WithFields(logrus.Fields{
			"filePath": filePath,
		}).Error("save file failed.")
		return "", errno.ErrFIleUpload().WithDetailMessage("save file failed.")
	}
	return filePath, nil
}

func container(obj interface{}, target interface{}) bool {
	targetValue := reflect.ValueOf(target)
	switch reflect.TypeOf(target).Kind() {
	case reflect.Array, reflect.Slice:
		for i := 0; i < targetValue.Len(); i++ {
			if targetValue.Index(i).Interface() == obj {
				return true
			}
		}
	case reflect.Map:
		if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() {
			return true
		}
	}
	return false
}
